#include "stdafx.h"

extern LPWSTR ServiceName;
extern CService srvc;

// clang-format off
static struct ErrEntry {
    int code;
    const char* msg;
} ErrList[] = {{ 0,    "No error" },
               { 1055, "The service database is locked." },
               { 1056, "An instance of the service is already running." },
               { 1060, "The service does not exist as an installed service." },
               { 1061, "The service cannot accept control messages at this time." },
               { 1062, "The service has not been started." },
               { 1063, "The service process could not connect to the service controller." },
               { 1064, "An exception occurred in the service when handling the control request." },
               { 1065, "The database specified does not exist." },
               { 1066, "The service has returned a service-specific error code." },
               { 1067, "The process terminated unexpectedly." },
               { 1068, "The dependency service or group failed to start." },
               { 1069, "The service did not start due to a logon failure." },
               { 1070, "After starting, the service hung in a start-pending state." },
               { 1071, "The specified service database lock is invalid." },
               { 1072, "The service marked for deletion." },
               { 1073, "The service already exists." },
               { 1078, "The name is already in use as either a service name or a service display name." },
              };
// clang-format on

const int nErrList = sizeof(ErrList) / sizeof(ErrEntry);

void ErrorHandler(char *s, int err)
{
    printf("Failed. Error %d ", err);
    int i;
    for (i = 0; i < nErrList; ++i)
    {
        if (ErrList[i].code == err)
        {
            printf("%s\n", ErrList[i].msg);
            break;
        }
    }
    if (i == nErrList)
    {
        printf("unknown error\n");
    }

    FILE *pLog = fopen("libtestsvc.log", "a");
    fprintf(pLog, "%s failed, error code = %d\n", s, err);
    fclose(pLog);

    ExitProcess(err);
}

void PrintMessage(char *s)
{
#ifdef DBG
    FILE *pLog = fopen("libtestsvc.log", "a");
    fprintf(pLog, "%s\n", s);
    fclose(pLog);
#endif
}

void ShowUsage()
{
    printf("\n");
    printf("USAGE:\n");
    printf("blnsvr -i\tInstall service\n");
    printf("blnsvr -u\tUninstall service\n");
    printf("blnsvr -r\tRun service\n");
    printf("blnsvr -s\tStop service\n");
    printf("blnsvr -p\tPause service\n");
    printf("blnsvr -c\tResume service\n");
    printf("blnsvr status\tCurrent status\n");
    printf("\n");
}

BOOL InstallService()
{
    SC_HANDLE newService;
    SC_HANDLE scm;
    TCHAR szBuffer[255];
    TCHAR szPath[MAX_PATH];

    GetModuleFileName(GetModuleHandle(NULL), szPath, MAX_PATH);
    if (FAILED(StringCchCopy(szBuffer, 255, TEXT("\""))))
    {
        return FALSE;
    }
    if (FAILED(StringCchCat(szBuffer, 255, szPath)))
    {
        return FALSE;
    }
    if (FAILED(StringCchCat(szBuffer, 255, TEXT("\""))))
    {
        return FALSE;
    }

    scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
    if (scm == NULL)
    {
        ErrorHandler("OpenSCManager", GetLastError());
    }
    newService = CreateService(scm,
                               ServiceName,
                               ServiceName,
                               SERVICE_ALL_ACCESS,
                               SERVICE_WIN32_OWN_PROCESS,
                               SERVICE_AUTO_START,
                               SERVICE_ERROR_NORMAL,
                               szBuffer,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL);
    if (!newService)
    {
        ErrorHandler("CreateService", GetLastError());
        return FALSE;
    }
    else
    {
        printf("Service Installed\n");
        ServiceRun();
    }

    CloseServiceHandle(newService);
    CloseServiceHandle(scm);

    return TRUE;
}

BOOL UninstallService()
{
    SC_HANDLE service;
    SC_HANDLE scm;
    BOOL res;
    SERVICE_STATUS status;

    scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!scm)
    {
        ErrorHandler("OpenSCManager", GetLastError());
    }

    service = OpenService(scm, ServiceName, SERVICE_ALL_ACCESS | DELETE);
    if (!service)
    {
        ErrorHandler("OpenService", GetLastError());
    }

    res = QueryServiceStatus(service, &status);
    if (!res)
    {
        ErrorHandler("QueryServiceStatus", GetLastError());
    }

    if (status.dwCurrentState != SERVICE_STOPPED)
    {
        printf("Stopping service...\n");
        res = ControlService(service, SERVICE_CONTROL_STOP, &status);
        if (!res)
        {
            ErrorHandler("ControlService", GetLastError());
        }
        Sleep(5000);
    }

    res = DeleteService(service);
    if (res)
    {
        printf("Service Uninstalled\n");
    }
    else
    {
        ErrorHandler("DeleteService", GetLastError());
    }

    CloseServiceHandle(service);
    CloseServiceHandle(scm);

    return TRUE;
}

BOOL ServiceRun()
{
    SC_HANDLE scm, Service;
    SERVICE_STATUS ssStatus;
    DWORD dwOldCheckPoint;
    DWORD dwStartTickCount;
    DWORD dwWaitTime;
    DWORD dwStatus;

    scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!scm)
    {
        ErrorHandler("OpenSCManager", GetLastError());
    }

    Service = OpenService(scm, ServiceName, SERVICE_ALL_ACCESS);
    if (!Service)
    {
        ErrorHandler("OpenService", GetLastError());
        return FALSE;
    }
    else
    {
        StartService(Service, 0, NULL);
        srvc.GetStatus(Service);

        if (!QueryServiceStatus(Service, &ssStatus))
        {
            ErrorHandler("QueryServiceStatus", GetLastError());
        }
        dwStartTickCount = GetTickCount();
        dwOldCheckPoint = ssStatus.dwCheckPoint;

        while (ssStatus.dwCurrentState == SERVICE_START_PENDING)
        {
            dwWaitTime = ssStatus.dwWaitHint / 10;

            if (dwWaitTime < 1000)
            {
                dwWaitTime = 1000;
            }
            else if (dwWaitTime > 10000)
            {
                dwWaitTime = 10000;
            }

            Sleep(dwWaitTime);

            if (!QueryServiceStatus(Service, &ssStatus))
            {
                break;
            }

            if (ssStatus.dwCheckPoint > dwOldCheckPoint)
            {
                dwStartTickCount = GetTickCount();
                dwOldCheckPoint = ssStatus.dwCheckPoint;
            }
            else
            {
                if (GetTickCount() - dwStartTickCount > ssStatus.dwWaitHint)
                {
                    break;
                }
            }
        }

        if (ssStatus.dwCurrentState == SERVICE_RUNNING)
        {
            srvc.GetStatus(Service);
            dwStatus = NO_ERROR;
        }
        else
        {

            printf("\nService not started.\n");
            printf("  Current State: %d\n", ssStatus.dwCurrentState);
            printf("  Exit Code: %d\n", ssStatus.dwWin32ExitCode);
            printf("  Service Specific Exit Code: %d\n", ssStatus.dwServiceSpecificExitCode);
            printf("  Check Point: %d\n", ssStatus.dwCheckPoint);
            printf("  Wait Hint: %d\n", ssStatus.dwWaitHint);
            dwStatus = GetLastError();
        }
    }

    CloseServiceHandle(scm);
    CloseServiceHandle(Service);
    return TRUE;
}

BOOL ServiceControl(int ctrl)
{
    SC_HANDLE service;
    SC_HANDLE scm;
    BOOL res;
    SERVICE_STATUS status;

    scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!scm)
    {
        ErrorHandler("OpenSCManager", GetLastError());
    }

    service = OpenService(scm, ServiceName, SERVICE_ALL_ACCESS);
    if (!service)
    {
        ErrorHandler("OpenService", GetLastError());
    }

    if (ctrl == SERVICE_CONTROL_STOP)
    {
        printf("Service is stopping...\n");
        res = ControlService(service, SERVICE_CONTROL_STOP, &status);
    }
    else if (ctrl == SERVICE_CONTROL_PAUSE)
    {
        printf("Service is pausing...\n");
        res = ControlService(service, SERVICE_CONTROL_PAUSE, &status);
    }
    else if (ctrl == SERVICE_CONTROL_CONTINUE)
    {
        printf("Service is resuming...\n");
        res = ControlService(service, SERVICE_CONTROL_CONTINUE, &status);
    }

    if (!res)
    {
        ErrorHandler("ControlService", GetLastError());
    }
    else
    {
        srvc.GetStatus(service);
    }

    CloseServiceHandle(service);
    CloseServiceHandle(scm);

    return TRUE;
}

BOOL GetConfiguration()
{
    SC_HANDLE service;
    SC_HANDLE scm;
    BOOL res;
    LPQUERY_SERVICE_CONFIG buffer;
    DWORD sizeNeeded;

    scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!scm)
    {
        ErrorHandler("OpenSCManager", GetLastError());
    }

    service = OpenService(scm, ServiceName, SERVICE_QUERY_CONFIG);
    if (!service)
    {
        ErrorHandler("OpenService", GetLastError());
    }

    buffer = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LPTR, 4096);
    res = QueryServiceConfig(service, buffer, 4096, &sizeNeeded);
    if (!res)
    {
        ErrorHandler("QueryServiceConfig", GetLastError());
    }

    printf("Service name:\t%s\n", buffer->lpDisplayName);
    printf("Service type:\t%d\n", buffer->dwServiceType);
    printf("Start type:\t%d\n", buffer->dwStartType);
    printf("Start name:\t%s\n", buffer->lpServiceStartName);
    printf("Path:\t\t%s\n", buffer->lpBinaryPathName);

    LocalFree(buffer);

    CloseServiceHandle(service);
    CloseServiceHandle(scm);
    return TRUE;
}

BOOL ChangeConfig()
{
    SC_HANDLE service;
    SC_HANDLE scm;
    BOOL res;
    SC_LOCK lock;

    scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS | GENERIC_WRITE);
    if (!scm)
    {
        ErrorHandler("OpenSCManager", GetLastError());
    }
    lock = LockServiceDatabase(scm);
    if (lock == 0)
    {
        ErrorHandler("LockServiceDatabase", GetLastError());
    }
    service = OpenService(scm, ServiceName, SERVICE_ALL_ACCESS);
    if (!service)
    {
        ErrorHandler("OpenService", GetLastError());
    }
    res = ChangeServiceConfig(service,
                              SERVICE_NO_CHANGE,
                              SERVICE_NO_CHANGE,
                              SERVICE_NO_CHANGE,
                              NULL,
                              NULL,
                              NULL,
                              NULL,
                              NULL,
                              NULL,
                              NULL);
    if (!res)
    {
        UnlockServiceDatabase(lock);
        ErrorHandler("ChangeServiceConfig", GetLastError());
    }

    res = UnlockServiceDatabase(lock);
    if (!res)
    {
        ErrorHandler("UnlockServiceDatabase", GetLastError());
    }
    CloseServiceHandle(service);
    CloseServiceHandle(scm);
    return TRUE;
}
